require "engine.class"
require "engine.GameTurnBased"
require "engine.interface.GameMusic"
require "engine.interface.GameSound"
require "engine.interface.GameTargeting"
local KeyBind = require "engine.KeyBind"
local Savefile = require "engine.Savefile"
local DamageType = require "engine.DamageType"
local Zone = require "engine.Zone"
local Map = require "engine.Map"
local Level = require "engine.Level"
local Birther = require "engine.Birther"
local Astar = require "engine.Astar"
local DirectPath = require "engine.DirectPath"
local Shader = require "engine.Shader"

--local GameState = require "mod.class.GameState"
--local Store = require "mod.class.Store"
--local Trap = require "mod.class.Trap"
local Grid = require "mod.class.Grid"
local Actor = require "mod.class.Actor"
local ActorStats = require "engine.interface.ActorStats"
local ActorResource = require "engine.interface.ActorResource"
local ActorTalents = require "engine.interface.ActorTalents"
local ActorAI = require "engine.interface.ActorAI"
local Player = require "mod.class.Player"
local NPC = require "mod.class.NPC"

local PlayerDisplay = require "mod.class.PlayerDisplay"
local MonsterDisplay = require "mod.class.MonsterDisplay"
local HotkeysDisplay = require "engine.HotkeysDisplay"
local ActorsSeenDisplay = require "mod.class.MonsterTactical" --"engine.ActorsSeenDisplay"
local LogDisplay = require "engine.LogDisplay"
local LogFlasher = require "engine.LogFlasher"
local DebugConsole = require "engine.DebugConsole"
local FlyingText = require "engine.FlyingText"
local Tooltip = require "engine.Tooltip"
local Calendar = require "engine.Calendar"

local Dialog = require "engine.ui.Dialog"
local QuitDialog = require "mod.dialogs.Quit"
local MapMenu = require "mod.dialogs.MapMenu"

local monster_mode = 1

module(..., package.seeall, class.inherit(engine.GameTurnBased, engine.interface.GameMusic, engine.interface.GameSound, engine.interface.GameTargeting))

function _M:init()
	engine.GameTurnBased.init(self, engine.KeyBind.new(), 1000, 100)

	self.persistant_actors = {}

	-- Pause at birth
	self.paused = true

	-- Same init as when loaded from a savefile
	self:loaded()
end

function _M:run()
	self.flash = LogFlasher.new(0, 0, self.w, 20, nil, nil, nil, {255,255,255}, {0,0,0})
	self.logdisplay = LogDisplay.new(0, self.h * 0.8, self.w * 0.5 - 18, self.h * 0.2, nil, nil, nil, {255,255,255}, "/data/gfx/ui/message-log.png")
	self.player_display = PlayerDisplay.new(0, 220, 200, self.h * 0.8 - 220, {30,30,0})
	self.npcs_display = ActorsSeenDisplay.new(nil, self.w * 0.5, self.h * 0.8, self.w * 0.5, self.h * 0.2, "/data/gfx/ui/talents-list.png")
	self.npc_display = MonsterDisplay.new(nil, self.w * 0.5, self.h * 0.8, self.w * 0.5, self.h * 0.2, "/data/gfx/ui/talents-list.png")
	self.tooltip = Tooltip.new(nil, nil, {255,255,255}, {30,30,30})
	self.flyers = FlyingText.new()
	self:setFlyingText(self.flyers)
	self.minimap_bg, self.minimap_bg_w, self.minimap_bg_h = core.display.loadImage("/data/gfx/ui/minimap.png"):glTexture()
	self:createSeparators()

	self.log = function(style, ...) if type(style) == "number" then self.logdisplay(...) self.flash(style, ...) else self.logdisplay(style, ...) self.flash(self.flash.NEUTRAL, style, ...) end end
	self.logSeen = function(e, style, ...) if e and self.level.map.seens(e.x, e.y) then self.log(style, ...) end end
	self.logPlayer = function(e, style, ...) if e == self.player then self.log(style, ...) end end

	self.log(self.flash.GOOD, "Welcome to #00FF00#Elementalis#LAST#, valley of the elements!")

	-- Setup inputs
	self:setupCommands()
	self:setupMouse()

	-- Starting from here we create a new game
	if not self.player then self:newGame() end
	
	self.npcs_display.actor = self.player
	self.npc_display.actor = self.player

	self:initTargeting()

	-- Ok everything is good to go, activate the game in the engine!
	self:setCurrent()

	if self.level then self:setupDisplayMode() end
end

function _M:newGame()
	self.player = Player.new{name=self.player_name, game_ender=true}
	Map:setViewerActor(self.player)
	self:setupDisplayMode()

	local birth = Birther.new(self.player, {"base", "element" }, function()
		self:changeLevel(2, "town-castle")
		print("[PLAYER BIRTH] resolve...")
		self.player.life = self.player.max_life
		self.player:resolve()
		self.player:resolve(nil, true)
		self.player.energy.value = self.energy_to_act
		self.always_target = true
		Map:setViewerFaction(self.player.faction)
		self.paused = true
		print("[PLAYER BIRTH] resolved!")
	end)
	self:registerDialog(birth)
end

function _M:loaded()
	engine.GameTurnBased.loaded(self)
	Zone:setup{npc_class="mod.class.NPC", grid_class="mod.class.Grid", object_class="mod.class.Object", }
	Map:setViewerActor(self.player)
	Map:setViewPort(200, 20, self.w - 200, math.floor(self.h * 0.80) - 20, 32, 32, nil, 22, true, true)
	if self.player then self.player.changed = true end
	self.key = engine.KeyBind.new()

	if self.always_target then Map:setViewerFaction(self.player.faction) end
end

function _M:createSeparators()
	self.bottom_separator, self.bottom_separator_w, self.bottom_separator_h = self:createVisualSeparator("horizontal", self.w)
	self.split_separator, self.split_separator_w, self.split_separator_h = self:createVisualSeparator("vertical", math.floor(self.h * 0.2))
	self.player_separator, self.player_separator_w, self.player_separator_h = self:createVisualSeparator("vertical", math.floor(self.h * 0.8) - 20)
end

function _M:setupDisplayMode()
	print("[DISPLAY MODE] 32x32 GFX")
	Map:setViewPort(200, 20, self.w - 200, math.floor(self.h * 0.80) - 20, 32, 32, nil, 22, true, true)
	Map:resetTiles()
	Map.tiles.use_images = true

	if self.level then
		self.level.map:recreate()
		engine.interface.GameTargeting.init(self)
		self.level.map:moveViewSurround(self.player.x, self.player.y, 8, 8)
	end
end


function _M:initTargeting()
	engine.interface.GameTargeting.init(self)
	self.target.on_set_target = function(self)
--		if game.key == game.targetmode_key then game.level.map:moveViewSurround(self.target.x, self.target.y, 8, 8) end
	end
end

function _M:setupMiniMap()
	if self.level and self.level.map then self.level.map._map:setupMiniMapGridSize(4) end
end

function _M:save()
	return class.save(self, self:defaultSavedFields{persistant_actors=true}, true)
end

function _M:getSaveDescription()
	return {
		name = self.player.name,
		description = ([[Exploring level %d of %s.]]):format(self.level.level, self.zone.name),
	}
end

function _M:leaveLevel(level, lev, old_lev)
	self.to_re_add_actors = {}
	if level:hasEntity(self.player) then
		level.exited = level.exited or {}
		if lev > old_lev then
			level.exited.down = {x=self.player.x, y=self.player.y}
		else
			level.exited.up = {x=self.player.x, y=self.player.y}
		end
		level.last_turn = game.turn
		for act, _ in pairs(self.persistant_actors) do
			if level:hasEntity(act) then
				level:removeEntity(act)
				self.to_re_add_actors[act] = true
			end
		end
		level:removeEntity(self.player)
	end
end

function _M:changeLevel(lev, zone)
	if not self.player.game_ender then
		game.logPlayer(self.player, "#LIGHT_RED#You may not change level without your own body!")
		return
	end

	if self.zone and self.zone.on_leave then
		local nl, nz = self.zone.on_leave(lev, old_lev, zone)
		if nl then lev = nl end
		if nz then zone = nz end
	end

	local old_lev = (self.level and not zone) and self.level.level or -1000
	local old_zone = "nothing"
	if zone then
		if self.zone then
			old_zone = self.zone.name
			self.zone:leaveLevel(false, lev, old_lev)
			self.zone:leave()
		end
		if type(zone) == "string" then
			self.zone = Zone.new(zone)
		else
			self.zone = zone
		end
	end
	self.zone:getLevel(self, lev, old_lev)

	-- Decay level ?
	if self.level.last_turn and self.level.data.decay and self.level.last_turn + self.level.data.decay[1] * 10 < game.turn then
		local nb_actor, remain_actor = self.level:decay(Map.ACTOR, function(e) return not e.unique and self.level.last_turn + rng.range(self.level.data.decay[1], self.level.data.decay[2]) < game.turn * 10 end)
--		local nb_object, remain_object = self.level:decay(Map.OBJECT, function(e) return not e.unique and self.level.last_turn + rng.range(self.level.data.decay[1], self.level.data.decay[2]) < game.turn * 10 end)

		local gen = self.zone:getGenerator("actor", self.level)
		if gen.regenFrom then gen:regenFrom(remain_actor) end

--		local gen = self.zone:getGenerator("object", self.level)
--		if gen.regenFrom then gen:regenFrom(remain_object) end
	end

	-- Move player to correct position when changing levels or zones
	if self.zone.name == "Homecastle" then
		if old_zone == "Path of War" then	--	went from warpath to homecastle
			self.player:move(54, 48, true)
		elseif old_lev == 1 then			--	went into castle
			self.player:move(9, 19, true)
		elseif old_lev == 2 then			--	left castle
			self.player:move(48, 15, true)
		else
			self.player:move(9, 3, true)	--	handling start of the game ;)
			self.player:SetupDamageTypes()	--	set up resists and damtype for the player
			print("[PLAYER] Set up resists and DamageType.")
			self.level.map:particleEmitter(9, 3, 3, "trans-portal", {radius=3})
		end
	elseif self.zone.name == "Path of War" then
		if old_zone == "Air Temple" then	--	went from air to warpath
			self.player:move(15, 35, true)
			self.level.map:particleEmitter(15, 35, 1, "trans-portal", {radius=1})
		elseif old_zone == "Earth Temple" then	--	went from earth to warpath
			self.player:move(35, 15, true)
			self.level.map:particleEmitter(35, 15, 1, "trans-portal", {radius=1})
		elseif old_zone == "Fire Temple" then	--	went from fire to warpath
			self.player:move(15, 15, true)
			self.level.map:particleEmitter(15, 15, 1, "trans-portal", {radius=1})
		elseif old_zone == "Water Temple" then	--	went from water to warpath
			self.player:move(35, 35, true)
			self.level.map:particleEmitter(35, 35, 1, "trans-portal", {radius=1})
		elseif old_zone == "Mana Temple" or old_zone == "Chaos Temple" then	--	went from mana or chaos to warpath
			self.player:move(25, 25, true)
			self.level.map:particleEmitter(25, 25, 1, "trans-portal", {radius=1})
		elseif old_lev == 6 and lev == 5 then	--	went up from lvl 6
			self.player:move(0, 48, true)
		elseif lev > old_lev then
			self.player:move(self.level.default_up.x, self.level.default_up.y, true)
		else
			self.player:move(self.level.default_down.x, self.level.default_down.y, true)
		end
	else	
		if lev > old_lev then
			self.player:move(self.level.default_up.x, self.level.default_up.y, true)
		else
			self.player:move(self.level.default_down.x, self.level.default_down.y, true)
		end
	end
	self.player.changed = true

	-- adjusting the tides of war
	if self.zone.name == "Path of War" then self.player.tides_of_war = math.min(10, math.max(0, self.player.tides_of_war + rng.range(-1,1))) end

	if self.to_re_add_actors then for act, _ in pairs(self.to_re_add_actors) do
		local x, y = util.findFreeGrid(self.player.x, self.player.y, 20, true, {[Map.ACTOR]=true})
		if x then act:move(x, y, true) end
	end end

	-- Re add entities
	self.level:addEntity(self.player)
	if self.to_re_add_actors then for act, _ in pairs(self.to_re_add_actors) do
		self.level:addEntity(act)
		act:setTarget(nil)
	end end


	if self.zone.on_enter then
		self.zone.on_enter(lev, old_lev, zone)
	end

--[[	I dont use this at all
	if self.level.data.ambiant_music then
		if self.level.data.ambiant_music ~= "last" then
			self:playMusic(self.level.data.ambiant_music)
		end
	else
		self:stopMusic()
	end]]

	-- Update the minimap
	self:setupMiniMap()

	-- Tell the map to use path strings to speed up path calculations
	for uid, e in pairs(self.level.entities) do
		self.level.map:addPathString(e:getPathString())
	end
	self.zone_name_s = nil
	self.level.map:redisplay()

	-- Messages upon entering the elemental temples
	if self.zone.name == "Air Temple" and old_zone == "Path of War" then
		self.logPlayer(self.player, "#YELLOW#You find yourself thrust high into the air, yet the 'ground' below you feels quite firm!")
		self.level.map:particleEmitter(self.player.x, self.player.y, 1, "trans-portal", {radius=1})
	elseif self.zone.name == "Earth Temple" and old_zone == "Path of War" then
		self.logPlayer(self.player, "#UMBER#You find yourself deep underground, and the walls and floors seem to close in on you!")
		self.level.map:particleEmitter(self.player.x, self.player.y, 1, "trans-portal", {radius=1})
	elseif self.zone.name == "Fire Temple" and old_zone == "Path of War" then
		self.logPlayer(self.player, "#RED#You find yourself engulfed in flames of fire and the blazing heat burns you!")
		self.level.map:particleEmitter(self.player.x, self.player.y, 1, "trans-portal", {radius=1})
	elseif self.zone.name == "Water Temple" and old_zone == "Path of War" then
		self.logPlayer(self.player, "#BLUE#You find yourself submerged in deep water, fortunately you dont need to breathe!")
		self.level.map:particleEmitter(self.player.x, self.player.y, 1, "trans-portal", {radius=1})
	elseif self.zone.name == "Mana Temple" and old_zone == "Path of War" then
		if self.player.faction == "chaos" then self.logPlayer(self.player, "#ORCHID#You find yourself in a strange place, everything seems calm and peaceful here, and it is driving you nuts!")
		else self.logPlayer(self.player, "#ORCHID#You find yourself in a strange place, everything seems calm and peaceful here!") end
		self.level.map:particleEmitter(self.player.x, self.player.y, 1, "trans-portal", {radius=1})
	elseif self.zone.name == "Chaos Temple" and old_zone == "Path of War" then
		if self.player.faction == "chaos" then self.logPlayer(self.player, "#LIGHT_GREEN#You find yourself strangely distorted, everything around you is blurry and in a constant state of flux! Ah, home sweet home!")
		else self.logPlayer(self.player, "#LIGHT_GREEN#You find yourself strangely distorted, everything around you is blurry and in a constant state of flux!") end
		self.level.map:particleEmitter(self.player.x, self.player.y, 1, "trans-portal", {radius=1})
	end
end

function _M:getPlayer()
	return self.player
end

--- Update the zone name, if needed
function _M:updateZoneName()
	local name
	if self.zone.display_name then
		name = self.zone.display_name()
	else
		local lev = self.level.level
		if self.level.data.reverse_level_display then lev = 1 + self.level.data.max_level - lev end
		name = ("%s (%d)"):format(self.zone.name, lev)
	end
	if self.zone_name_s and self.old_zone_name == name then return end

	self.player_display.font:setStyle("bold")
	local s = core.display.drawStringBlendedNewSurface(self.player_display.font, name, unpack(colors.simple(colors.GOLD)))
	self.player_display.font:setStyle("normal")
	self.zone_name_w, self.zone_name_h = s:getSize()
	self.zone_name_s, self.zone_name_tw, self.zone_name_th = s:glTexture()
	self.old_zone_name = name
	print("Updating zone name", name)
end

function _M:tick()
	if self.level then
		if self.target.target.entity and not self.level:hasEntity(self.target.target.entity) then self.target.target.entity = false end

		engine.GameTurnBased.tick(self)
		-- Fun stuff: this can make the game realtime, although callit it in display() will make it work better
		-- (since display is on a set FPS while tick() ticks as much as possible
		-- engine.GameEnergyBased.tick(self)
	end
	-- When paused (waiting for player input) we return true: this means we wont be called again until an event wakes us
	if self.paused and not savefile_pipe.saving then return true end
end

--- Called every game turns
-- Does nothing, you can override it
function _M:onTurn()
	-- The following happens only every 10 game turns (once for every turn of 1 mod speed actors)
	if self.turn % 10 ~= 0 then return end

	-- Process overlay effects
	self.level.map:processEffects()
end

function _M:display()
	-- If switching resolution, blank everything but the dialog
	if self.change_res_dialog then engine.GameTurnBased.display(self) return end

	-- Now the map, if any
	if self.level and self.level.map and self.level.map.finished then
		-- Display the map and compute FOV for the player if needed
		if self.level.map.changed then
			self.player:playerFOV()
		end

		-- Display using Framebuffer, sotaht we can use shaders and all
		if self.fbo then
			self.fbo:use(true)

			if self.level.data.background then self.level.data.background(self.level, 0, 0) end
			self.level.map:display(0, 0)
			self.target:display(0, 0)
			if self.level.data.foreground then self.level.data.foreground(self.level, 0, 0) end

			self.fbo:use(false)
			_2DNoise:bind(1, false)
			self.fbo:toScreen(
				self.level.map.display_x, self.level.map.display_y,
				self.level.map.viewport.width, self.level.map.viewport.height,
				self.fbo_shader.shad
			)

		-- Basic display
		else
			if self.level.data.background then self.level.data.background(self.level, self.level.map.display_x, self.level.map.display_y) end
			self.level.map:display()
			self.target:display()
			if self.level.data.foreground then self.level.data.foreground(self.level, self.level.map.display_x, self.level.map.display_y) end
		end

		if not self.zone_name_s then self:updateZoneName() end
		self.zone_name_s:toScreenFull(
			self.level.map.display_x + self.level.map.viewport.width - self.zone_name_w,
			self.level.map.display_y + self.level.map.viewport.height - self.zone_name_h,
			self.zone_name_w, self.zone_name_h,
			self.zone_name_tw, self.zone_name_th
		)

		-- Minimap display
		self.minimap_bg:toScreenFull(0, 20, 200, 200, self.minimap_bg_w, self.minimap_bg_h)
		self.level.map:minimapDisplay(0, 20, util.bound(self.player.x - 25, 0, self.level.map.w - 50), util.bound(self.player.y - 25, 0, self.level.map.h - 50), 50, 50, 1)
	end

	-- We display the player's interface
	self.flash:toScreen()
	self.logdisplay:toScreen()
	self.player_display:toScreen()
	if self.show_npc_list then
		self.npcs_display:toScreen()
	else
		self.npc_display:toScreen()
	end
	if self.player then self.player.changed = false end

	-- Separators
	self.bottom_separator:toScreenFull(0, 20 - 3, self.w, 6, self.bottom_separator_w, self.bottom_separator_h)
	self.bottom_separator:toScreenFull(0, self.h * 0.8 - 3, self.w, 6, self.bottom_separator_w, self.bottom_separator_h)
	self.split_separator:toScreenFull(self.w * 0.5 - 3 - 15, self.h * 0.8, 6, self.h * 0.2, self.split_separator_w, self.split_separator_h)
	self.split_separator:toScreenFull(self.w * 0.5 - 3, self.h * 0.8, 6, self.h * 0.2, self.split_separator_w, self.split_separator_h)
	self.player_separator:toScreenFull(200 - 3, 20, 6, self.h * 0.8 - 20, self.player_separator_w, self.player_separator_h)

	-- Icons
--	self:displayUIIcons()

	engine.GameTurnBased.display(self)

	-- Tooltip is displayed over all else, even dialogs
	self:targetDisplayTooltip(self.w, self.h)
end

--- Setup the keybinds
function _M:setupCommands()
	-- Make targeting work
	self.normal_key = self.key
	self:targetSetupKey()
	self.normal_key = self.key

	-- Activate profiler keybinds
	self.key:setupProfiler()

		self.key:addCommands{[{"_d","ctrl"}] = function()	self:changeLevel(5, "warpath") self.player:forceLevelup(25) self.player.combat_bonus = self.player.combat_bonus + 15	end,}
--		self.key:addCommands{[{"_f","ctrl"}] = function()	self:changeLevel(3, "temple-earth")	end,}
--		self.key:addCommands{[{"_g","ctrl"}] = function()	self:changeLevel(3, "temple-mana")	end,}
--		self.key:addCommands{[{"_e","ctrl"}] = function()	self.player:forceLevelup(20)	self:changeLevel(3, "temple-chaos")	end,}

	-- One key handled for normal function
	self.key:addBinds
	{
		-- Movements
		MOVE_LEFT = function() self.player:moveDir(4) end,
		MOVE_RIGHT = function() self.player:moveDir(6) end,
		MOVE_UP = function() self.player:moveDir(8) end,
		MOVE_DOWN = function() self.player:moveDir(2) end,
		MOVE_LEFT_UP = function() self.player:moveDir(7) end,
		MOVE_LEFT_DOWN = function() self.player:moveDir(1) end,
		MOVE_RIGHT_UP = function() self.player:moveDir(9) end,
		MOVE_RIGHT_DOWN = function() self.player:moveDir(3) end,
		MOVE_STAY = function() self.player:useEnergy() end,

		RUN_LEFT = function() self.player:runInit(4) end,
		RUN_RIGHT = function() self.player:runInit(6) end,
		RUN_UP = function() self.player:runInit(8) end,
		RUN_DOWN = function() self.player:runInit(2) end,
		RUN_LEFT_UP = function() self.player:runInit(7) end,
		RUN_LEFT_DOWN = function() self.player:runInit(1) end,
		RUN_RIGHT_UP = function() self.player:runInit(9) end,
		RUN_RIGHT_DOWN = function() self.player:runInit(3) end,

		-- Hotkeys
		HOTKEY_1 = function() self.player:activateHotkey(1) end,
		HOTKEY_2 = function() self.player:activateHotkey(2) end,
		HOTKEY_3 = function() self.player:activateHotkey(3) end,
		HOTKEY_4 = function() self.player:activateHotkey(4) end,
		HOTKEY_5 = function() self.player:activateHotkey(5) end,
		HOTKEY_6 = function() self.player:activateHotkey(6) end,
		HOTKEY_7 = function() self.player:activateHotkey(7) end,
		HOTKEY_8 = function() self.player:activateHotkey(8) end,
		HOTKEY_9 = function() self.player:activateHotkey(9) end,
		HOTKEY_10 = function() self.player:activateHotkey(10) end,
		HOTKEY_11 = function() self.player:activateHotkey(11) end,
		HOTKEY_12 = function() self.player:activateHotkey(12) end,
		HOTKEY_SECOND_1 = function() self.player:activateHotkey(13) end,
		HOTKEY_SECOND_2 = function() self.player:activateHotkey(14) end,
		HOTKEY_SECOND_3 = function() self.player:activateHotkey(15) end,
		HOTKEY_SECOND_4 = function() self.player:activateHotkey(16) end,
		HOTKEY_SECOND_5 = function() self.player:activateHotkey(17) end,
		HOTKEY_SECOND_6 = function() self.player:activateHotkey(18) end,
		HOTKEY_SECOND_7 = function() self.player:activateHotkey(19) end,
		HOTKEY_SECOND_8 = function() self.player:activateHotkey(20) end,
		HOTKEY_SECOND_9 = function() self.player:activateHotkey(21) end,
		HOTKEY_SECOND_10 = function() self.player:activateHotkey(22) end,
		HOTKEY_SECOND_11 = function() self.player:activateHotkey(23) end,
		HOTKEY_SECOND_12 = function() self.player:activateHotkey(24) end,
		HOTKEY_THIRD_1 = function() self.player:activateHotkey(25) end,
		HOTKEY_THIRD_2 = function() self.player:activateHotkey(26) end,
		HOTKEY_THIRD_3 = function() self.player:activateHotkey(27) end,
		HOTKEY_THIRD_4 = function() self.player:activateHotkey(28) end,
		HOTKEY_THIRD_5 = function() self.player:activateHotkey(29) end,
		HOTKEY_THIRD_6 = function() self.player:activateHotkey(30) end,
		HOTKEY_THIRD_7 = function() self.player:activateHotkey(31) end,
		HOTKEY_THIRD_8 = function() self.player:activateHotkey(31) end,
		HOTKEY_THIRD_9 = function() self.player:activateHotkey(33) end,
		HOTKEY_THIRD_10 = function() self.player:activateHotkey(34) end,
		HOTKEY_THIRD_11 = function() self.player:activateHotkey(35) end,
		HOTKEY_THIRD_12 = function() self.player:activateHotkey(36) end,
		HOTKEY_PREV_PAGE = function() self.player:prevHotkeyPage() end,
		HOTKEY_NEXT_PAGE = function() self.player:nextHotkeyPage() end,

		-- Actions
		CHANGE_LEVEL = function()
			local e = self.level.map(self.player.x, self.player.y, Map.TERRAIN)
			if self.player:enoughEnergy() and e.change_level then
				self:changeLevel(e.change_zone and e.change_level or self.level.level + e.change_level, e.change_zone)
			elseif self.player:enoughEnergy() and e.portal then
				self.player:move(e.portal[1], e.portal[2], true)
				self.level.map:particleEmitter(e.portal[1], e.portal[2], 1, "trans-portal", {radius=1})
			else
				self.log("There is no way out of this level here.")
			end
		end,

		REST = function()
			self.player:restInit()
		end,

		PICKUP_FLOOR = function()
			self.player:playerPickup()
		end,
		DROP_FLOOR = function()
			self.player:playerDrop()
		end,
		
		SHOW_INVENTORY = function()
			local d
			d = self.player:showEquipInven("Inventory", nil, function(o, inven, item, button, event)
				local ud = require("mod.dialogs.UseItemDialog").new(self.player, o, item, inven, function()
					d:generateList()
					d.title = "Inventory"
				end)
				self:registerDialog(ud)
			end)
		end,
		SHOW_EQUIPMENT = "SHOW_INVENTORY",
		
		WEAR_ITEM = function()
			self.player:playerWear()
		end,
		TAKEOFF_ITEM = function()
			self.player:playerTakeoff()
		end,
		USE_ITEM = function()
			self.player:playerUseItem()
		end,

		USE_TALENTS = function()
			self.player:useTalents()
		end,

		SAVE_GAME = function()
			self:saveGame()
		end,

		SHOW_QUESTS = function()
			self:registerDialog(require("engine.dialogs.ShowQuests").new(self.player))
		end,

		-- Exit the game
		QUIT_GAME = function()
			self:onQuit()
		end,

		-- Toggle monster list
		TOGGLE_NPC_LIST = function()
			self.show_npc_list = not self.show_npc_list
			self.player.changed = true
		end,

		SHOW_CHARACTER_SHEET = function()
--			self:registerDialog(require("mod.dialogs.CharacterSheet").new(self.player))
		end,

		EXIT = function()
			local menu menu = require("engine.dialogs.GameMenu").new{
				"resume",
				"keybinds",
				"resolution",
				"save",
				"quit"
			}
			self:registerDialog(menu)
		end,

		TACTICAL_DISPLAY = function()
			if Map.view_faction then
				self.always_target = nil
				Map:setViewerFaction(nil)
			else
				self.always_target = true
				Map:setViewerFaction(self.player.faction)
			end
		end,

		LOOK_AROUND = function()
			self.flash:empty(true)
			self.flash(self.flash.GOOD, "Looking around... (direction keys to select interresting things, shift+direction keys to move freely)")
			local co = coroutine.create(function() self.player:getTarget{type="hit", no_restrict=true, range=2000} end)
			local ok, err = coroutine.resume(co)
			if not ok and err then print(debug.traceback(co)) error(err) end
		end,
	}
	self.key:setCurrent()
end

function _M:setupMouse(reset)
	if reset then self.mouse:reset() end
	self.mouse:registerZone(Map.display_x, Map.display_y, Map.viewport.width, Map.viewport.height, function(button, mx, my, xrel, yrel, bx, by, event)
		-- Handle targeting
		if self:targetMouse(button, mx, my, xrel, yrel, event) then return end

		-- Handle Use menu
		if button == "right" and not xrel and not yrel and event == "button" then self:mouseRightClick(mx, my) return end

		-- Handle the mouse movement/scrolling
		self.player:mouseHandleDefault(self.key, self.key == self.normal_key, button, mx, my, xrel, yrel, event)
	end)
	-- Scroll message log
	self.mouse:registerZone(self.logdisplay.display_x, self.logdisplay.display_y, self.w, self.h, function(button)
		if button == "wheelup" then self.logdisplay:scrollUp(1) end
		if button == "wheeldown" then self.logdisplay:scrollUp(-1) end
	end, {button=true})
	if not reset then self.mouse:setCurrent() end
end

--- Right mouse click on the map
function _M:mouseRightClick(mx, my)
	local tmx, tmy = self.level.map:getMouseTile(mx, my)
	self:registerDialog(MapMenu.new(mx, my, tmx, tmy))
end

--- Ask if we realy want to close, if so, save the game first
function _M:onQuit()
	self.player:restStop()

	if not self.quit_dialog then
		self.quit_dialog = QuitDialog.new()
		self:registerDialog(self.quit_dialog)
	end
end

--- Requests the game to save
function _M:saveGame()
	-- savefile_pipe is created as a global by the engine
	savefile_pipe:push(self.save_name, "game", self)
	self.log("Saving game...")
end

function _M:setAllowedBuild(what, notify)
	profile.mod.allow_build = profile.mod.allow_build or {}
	if profile.mod.allow_build[what] then return end
	profile.mod.allow_build[what] = true

	profile:saveModuleProfile("allow_build", profile.mod.allow_build)

	if notify then
		self:registerDialog(require("mod.dialogs.UnlockDialog").new(what))
	end

	return true
end

--------------------------------------------------------------
-- UI stuff
--------------------------------------------------------------

--function _M:onPickUI(hits)
--	for i, uid in ipairs(hits) do
--		local e = __uids[uid]
--		if e then print(i, e.uid, e.name) end
--	end
--end

--- Create a visual separator
local _sep_left = core.display.loadImage("/data/gfx/ui/separator-left.png") _sep_left:alpha()
local _sep_right = core.display.loadImage("/data/gfx/ui/separator-right.png") _sep_right:alpha()
local _sep_horiz = core.display.loadImage("/data/gfx/ui/separator-hori.png") _sep_horiz:alpha()
local _sep_top = core.display.loadImage("/data/gfx/ui/separator-top.png") _sep_top:alpha()
local _sep_bottom = core.display.loadImage("/data/gfx/ui/separator-bottom.png") _sep_bottom:alpha()
local _sep_vert = core.display.loadImage("/data/gfx/ui/separator-vert.png") _sep_vert:alpha()
function _M:createVisualSeparator(dir, size)
	if dir == "horizontal" then
		local sep = core.display.newSurface(size, 6)
		sep:erase(0, 0, 0)
		sep:merge(_sep_left, 0, 0)
		for i = 7, size - 7, 9 do sep:merge(_sep_horiz, i, 0) end
		sep:merge(_sep_right, size - 6, 0)
		return sep:glTexture()
	else
		local sep = core.display.newSurface(6, size)
		sep:erase(0, 0, 0)
		sep:merge(_sep_top, 0, 0)
		for i = 7, size - 7, 9 do sep:merge(_sep_vert, 0, i) end
		sep:merge(_sep_bottom, 0, size - 6)
		return sep:glTexture()
	end
end
